/*!

   CueBox 1.0-RC2
   Photo gallery viewer widget by Ricardo Sanz.
   (c) Ricardo Sanz <http://code.google.com/p/cuebox/>
   MIT-style license.
   Heavily based on Slimbox v1.63 by Christophe Beyls
	(c) 2007-2008 Christophe Beyls <http://www.digitalia.be>
	MIT-style license.

   Feel free to use, modify or distribute this code as long as this advice
   is kept.

*/


var CueBox = new Class({

   // Implements
   Implements: Options,

   // Options.
   options: {
      loop: true,                // Allows to navigate between first and last images.
      bgPanelOpacity: 0.9,
      resizeTransition: false,   // Default mootools transition for resizing.
      resizeDuration: 400,       // Duration of each of the box resize animations (in milliseconds)
      initialWidth: 150,         // Initial width of the box (in pixels)
      initialHeight: 150,        // Initial height of the box (in pixels)
      imgFitToScreen: true,      // Image will be resized to window size.
      allowResize: true,         // Allow user to change between real size and fit to screen size.
      twoStepsResizing: false,   // Box resizing first width then height, instead both simultaneus.
      showCounter: true,         // If true, a counter will only be shown if there is more than 1 image to display.
      counterText: 'Image {x} of {y}.',  // Change or translate it. x is replaced by actual image, y by total number of images.
      errorMsg: 'Error loading image.',  // Change or transalte it.
      imgTooBigWarning: 'This image is bigger than actual window size so you will not see it complete and you may get web page render errors. Would you like to switch to Fit-To-Screen mode?',
      fitMsg: 'Fit to screen',
      realSzMsg: 'Real size',
      nextMsg: 'Next image',
      prevMsg: 'Previous image',
      closeMsg: 'Close viewer',
      dlMsg: 'Download all images',
      hideScrolls: null,         // Elements with scroll to hide.
      zipPath: null,
      startImg: 0
   },


   //initialization
   initialize: function(options) {
      // Set and save initial options (you can use options in open function just for one use).
      this.setOptions(options);
      this.initialOptions = $H(this.options);


      // Class variables.
      //

      // DOM objects.
      this.bgPanel,
      this.hold,
      this.info,
      this.nextBt,
      this.prevBt,
      this.closeBt,
      this.zipBt,
      this.caption,
      this.number,
      this.plImg,
      this.plPrevImg = new Image(),
      this.plNextImg = new Image(),

      // Other variables.
      this.imgDSize = {},
      this.isInfoVisible,
      this.startImg,
      this.elementsStyle = {};

      // Initializa DOM elements (create and insert them).
      $(document.body).adopt($$([
            this.bgPanel = new Element('div', {id: 'vBgPanel'}),
            this.hold = new Element('div', {id: 'vHold'}).grab(
               this.imgPanel = new Element('div', {id: 'vImgPanel'})
            ),
            this.info = new Element('div', {id: 'vInfo'}).adopt(
               this.prevBt = new Element('a', {id: 'vPrevBt',
                                             href: '#',
                                             title: this.options.prevMsg}).addEvent('click', this.previous.bindWithEvent(this)),
               this.nextBt = new Element('a', {id: 'vNextBt',
                                             href: '#',
                                             title: this.options.nextMsg}).addEvent('click', this.next.bindWithEvent(this)),
               new Element('div', {styles: {clear: 'both'}}),
               this.caption = new Element('div', {id: 'vCaption', html: '[Caption]'}),
               this.closeBt = new Element('a', {id: 'vCloseBt',
                                                href: '#',
                                                title: this.options.closeMsg}).addEvent('click', this.close.bindWithEvent(this)),
               this.fitBt = new Element('a', {id: 'vFitBt',
                                             href: '#'}).addEvent('click', this.toggleSize.bindWithEvent(this)),
               this.zipBt = new Element('a', {id: 'vZipBt',
                                              href: '#',
                                              title: this.options.dlMsg}),

               this.number = new Element('div', {id: 'vNumber', html: '[Number]'})
            )
         ]).setStyle('visibility', 'hidden')
      );


      // Bounded functions for events.
      this.positionBounded = this.position.bind(this);
      this.keyDownBounded = this.keyDown.bind(this);


      $$(this.fitBt, this.prevBt, this.nextBt, this.zipBt).setStyle('visibility', 'hidden');


      // Close on click event on background panel.
      this.bgPanel.addEvent("click", this.close.bindWithEvent(this));

      // Effects.
      this.fx = {
         bgPanel: new Fx.Tween(this.bgPanel, {property: "opacity", duration: 500}).set(0),
         hold: new Fx.Tween(this.hold, {property: "opacity", duration: 500/*, onComplete: nextEffect*/}),
         imgPanel: new Fx.Tween(this.imgPanel, {property: "opacity", duration: 150, onComplete: this.checkState.bindWithEvent(this)}).set(0),
         marginInfo: new Fx.Tween(this.info, {property: "marginTop", duration: 500, onComplete: this.checkState.bindWithEvent(this)})
      };

   },


   // Open viewer.
   open: function(images, options) {
         // Set options (first initial, then passed).
         this.setOptions(this.initialOptions.getClean()).setOptions(options);

         // Do I have images?
         if (!$defined(images) || images.length == 0) {
            // No images.
            return;
         }

         this.setup(true);

         this.hold.setStyle('background-image', 'url(lib/css/loading.gif)');


         // Images list.
         this.images = images;

         this.startImg = this.options.startImg;

         // Actual image.
         this.actualImg = -1;

         // Previous and next image.
         this.nextImg = -1;
         this.prevImg = -1;

         // Save transition if given.
         this.transition = this.options.resizeTransition ? {transition: this.options.resizeTransition} : {};


         // Set border size.

         // Loop allowed?
         this.options.loop = this.options.loop && (this.images.length > 1);

         // Initializing and positioning background.
         this.calculateVPosition();

         this.bgPanel.setStyles({top: this.winScroll,
                                 height: this.bgPanelHeight});

         this.info.setStyle('top', this.infoTop);

         this.hold.setStyles({top: this.winScroll + this.margin,
                                   width: this.options.initialWidth,
                                   height: this.options.initialHeight,
                                   marginLeft: -this.options.initialWidth/2,
                                   visibility: ""});


         // Main hold resize effect.
         this.fx.resize = new Fx.Morph(this.hold, $extend({duration: this.options.resizeDuration,
                                                           onComplete: this.checkState.bindWithEvent(this)}, this.transition));


         // Info panel morph effect.
         this.fx.info = new Fx.Morph(this.info, $extend({duration: this.options.resizeDuration,
                                                         link: 'chain'}, this.transition));

         this.fx.bgPanel.start(this.options.bgPanelOpacity);


         // Setup zip download.
         if (this.options.zipPath) this.zipBt.set('href', this.options.zipPath);

         // First action is to load the current image.
         this.changeImage(this.startImg);
   },


   checkState: function() {
      // Function that checks the actual state and decides the next state.

      switch(this.state) {
         case 1:
            // Image loaded state.

            this.plImg = $(this.plImg);

            // Image ratio.
            var ratio = this.plImg.width / this.plImg.height;

            // Get max dimensions.
            var maxDims = window.getSize();

            if (this.options.imgFitToScreen) {
               this.imgDSize = this.dimensions(ratio,
                                                (maxDims.x) * 0.9,
                                                (maxDims.y - this.margin * 3 - this.info.offsetHeight));
               $(this.plImg).setStyles({width: '100%', height: '100%'});
            } else {
               this.imgDSize.x = this.plImg.width;
               this.imgDSize.y = this.plImg.height;

               if ((this.plImg.width > maxDims.x - 30)
                     ||
                  (this.plImg.height > maxDims.y - this.margin -  this.info.offsetHeight - 20)) {
                  // Image is too big for actual window size.
                  if (confirm(this.options.imgTooBigWarning)) {
                     this.toggleSize();
                     return;
                  }
               }
            }

            this.calculateVPosition();

            // Prepare box to display image.
            this.state = this.options.twoStepsResizing ? 2 : 4;
            this.resizeHold();
            break;


         case 2,3:
            // X and Y resizing states.
            this.state = 3;
            this.resizeHold();
            break;

         case 4:
            // X and Y resizing state.
            this.state = 5
            this.checkState();
            break;

         case 5:
            // Show image state.

            // Insert image on imgPanel and show it.
            this.imgPanel.empty().grab(this.plImg).setStyles({width: this.hold.getSize().x - 20,
                                                              height: this.hold.getSize().y - 20});
            this.plImg = null;

            this.state = this.isInfoVisible ? 7 : 6;

            this.fx.imgPanel.start(1);
            break;

         case 6:
            // Show info state.

            // Get right position and size.
            this.info.setStyles({top: this.infoTop,
                                 width: this.hold.getStyle('width'),
                                 marginLeft: this.hold.getStyle('marginLeft'),
                                 marginTop: -this.info.offsetHeight,
                                 visibility: 'visible'});
            this.isInfoVisible = true;

            // Check prev and next buttons.
            this.checkButtons();
            this.setResizeBt();

            this.state = 7;
            this.fx.marginInfo.start(0);
            break;

         case 7:
            // Check prev and next buttons.
            this.checkButtons();
            $$(this.caption, this.number).setStyle('visibility', 'visible');
            this.allowChangeImage = true;
            break;

         case 8:
            if (!--this.fxIgnores) {
               // Terminating;
               $$(this.nextBt,
                  this.prevBt,
                  this.fitBt,
                  this.zipBt,
                  this.caption,
                  this.number,
                  this.hold,
                  this.info).setStyle('visibility', 'hidden');

                  this.isInfoVisible = false;
                  this.bgPanel.setStyle('display', 'none');
                  this.imgPanel.empty();
                  this.setup(false);
            }


            break;

         default:
            // Error, best is to close widget expecting that everything closes nicely.
            this.close();
      }
   },


   checkButtons: function() {
      this.prevBt.setStyle('visibility', (this.prevImg >= 0) ? 'visible': 'hidden');
      this.nextBt.setStyle('visibility', (this.nextImg >= 0) ? 'visible': 'hidden');
      this.fitBt.setStyle('visibility', 'visible');
      this.zipBt.setStyle('visibility', (this.options.zipPath) ? 'visible': 'hidden');


   },

   calculateVPosition: function () {

      this.winScroll = window.getScrollTop();
      this.bgPanelTop = this.winScroll
      this.bgPanelHeight = window.getHeight()
      this.margin = this.bgPanelHeight / 33;

      // Info position.
      this.infoTop = this.bgPanelTop + this.bgPanelHeight - this.margin - this.info.offsetHeight;

      // Hold position.
      this.holdTop = this.infoTop - this.imgDSize.y - 20;

   },

   position: function() {
      this.calculateVPosition();
      this.bgPanel.setStyle('top', this.bgPanelTop);
      this.info.setStyle('top', this.infoTop);
      this.hold.setStyle('top', this.holdTop);
   },

   resizeHold: function() {
      switch(this.state) {
         case 2:
            this.fx.resize.start({width: this.imgDSize.x,
                                  marginLeft: -this.imgDSize.x/2});
            this.fx.info.start({width: this.imgDSize.x,
                                 marginLeft: -this.imgDSize.x/2});

            break;

         case 3:
               this.fx.resize.start({height: this.imgDSize.y,
                                     top: this.holdTop});
               this.fx.info.start({top: this.infoTop});
            break;

         case 4:
            this.fx.resize.start({width: this.imgDSize.x,
                                  marginLeft: -this.imgDSize.x / 2,
                                  height: this.imgDSize.y,
                                  top: this.holdTop});


            this.fx.info.start({width: this.imgDSize.x,
                                marginLeft: -this.imgDSize.x / 2,
                                top: this.infoTop});
            break;

      }
   },


   previous: function() {
      this.changeImage(this.prevImg);
      return false;
   },

   next: function() {
      this.changeImage(this.nextImg);
      return false;
   },

   changeImage: function(imgIndex) {
      this.state = 1;
      this.allowChangeImage = false;

      $$(this.prevBt,
         this.nextBt,
         this.fitBt,
         this.zipBt,
         this.caption,
         this.number).setStyle('visibility', 'hidden');


      this.hold.setStyle('background-image', 'url(lib/css/loading.gif)');
      this.imgPanel.setStyle('opacity', '0');

      this.actualImg = imgIndex;
      this.prevImg = ((this.actualImg || !this.options.loop) ? this.actualImg : this.images.length) - 1;
      this.nextImg = this.actualImg + 1;
      if (this.nextImg == this.images.length) this.nextImg = this.options.loop ? 0 : -1;

      // Set image info.
      this.caption.set('html', this.images[this.actualImg][1] || '');
      this.number.set('html', (this.options.showCounter && (this.images.length > 1))
                                 ?
                                    this.options.counterText.replace(/{x}/, this.actualImg + 1).replace(/{y}/, this.images.length)
                                 :
                                    "");


      // Load actual, previous and next image.
      if (this.prevImg >= 0) this.plPrevImg.src = this.images[this.prevImg][0];
      if (this.nextImg >= 0) this.plNextImg.src = this.images[this.nextImg][0];
      this.plImg = new Image();
      this.plImg.onload = this.checkState.bindWithEvent(this);
      this.plImg.onerror = this.errorLoadingImg.bindWithEvent(this);

      this.plImg.src = this.images[this.actualImg][0];

   },

   errorLoadingImg: function() {
      this.hold.setStyle('background-image', 'none');
      this.imgPanel.set('html', '<div id="vErrorMsg">' + this.options.errorMsg + '</div>');
      this.state = 7;
      $('vErrorMsg').setStyle('marginTop', this.hold.offsetHeight / 2);
      this.fx.imgPanel.start(1);
   },

   close: function() {
      this.hold.setStyle('background-image', 'none');
      this.state = 8;
      this.fxIgnores = 2;
      this.fx.imgPanel.start(0);
      this.fx.marginInfo.start(-this.info.offsetHeight);
      this.fx.bgPanel.start(0);
      return false;
   },


   dimensions: function(ratio, maxX, maxY) {

      var rc = [];

      // Is it a vertical or horizontal image?
      if (ratio > 1)
      {
         // Horizontal.
         rc.x = maxX;
         rc.y = rc.x / ratio;

         // If it has similar x and y it may be wrong now.
         if (rc.y > maxY) {
            // Fixing, height is kept.
            rc.y = maxY;
            rc.x = ratio * rc.y;
         }
      } else {
         // Vertical.
            rc.y = maxY;
            rc.x = ratio * rc.y;
      }
      return rc;
   },


   toggleSize: function() {
      this.options.imgFitToScreen = !this.options.imgFitToScreen;
      this.setResizeBt();
      this.changeImage(this.actualImg);
      return false;
   },

   setResizeBt: function (){
      if (this.options.allowResize && this.isInfoVisible) {
         if (this.options.imgFitToScreen) {
            this.fitBt.setStyles({visibility: 'visible',
                                  backgroundImage: 'url(lib/css/fit.png)'});
            this.fitBt.set('title', this.options.fitMsg);

         } else {
            this.fitBt.setStyles({visibility: 'visible',
                                  backgroundImage:'url(lib/css/real.png)'});
            this.fitBt.set('title', this.options.realSzMsg);
         }

      } else {
         this.fitBt.setStyle('visibility', 'hidden');
      }
   },

   setup: function (on) {

      if (Browser.Platform.mac) {
         // Srolls bars on Mac makes weird effects on viewer, let's disable/enable them.
         $$(this.options.hideScrolls).setStyle('overflow', on ? 'hidden' : 'auto');
      }

      // Hide or unhide all OBJECT and EMBED (SELECT on IR) elements to avoid weird visualization errors.
      // Previous state is saved to avoid unhide hidden elements.
      ['object', window.ie ? 'select' : 'embed'].forEach(function(tag) {
         Array.forEach(document.getElementsByTagName(tag), function(el) {
            if (on) this.elementsStyle[el] = el.style.visibility;
            el.style.visibility = on ? 'hidden' : this.elementsStyle[el];
         });
      });

      this.bgPanel.setStyle('display', on ? '' : 'none');

      // Adding/removing scroll, resize and keydown events.
      var fn = on ? 'addEvent' : 'removeEvent';
      window[fn]('scroll', this.positionBounded);
      window[fn]('resize', this.positionBounded);
      document[fn]('keydown', this.keyDownBounded);


   },

   keyDown: function (event) {
      switch(event.code) {
         case 27: // Esc
         case 88: // 'x'
         case 67: // 'c'
            this.close();
            break;
         case 37: // Left arrow
         case 80: // 'p'
         case 65: // 'a'
            if (this.allowChangeImage) this.previous();
            break;
         case 39: // Right arrow
         case 78: // 'n'
         case 83: // 's'
            if (this.allowChangeImage) this.next();
      }
   },

   prepare: function(list, options, mapper, filter) {

      // Set default values for mapper and filter if not given.
      mapper = mapper || function(el) {
         return [el.href, el.title];
      };

      filter = filter || function(el) {
         return true;
      }
      var privViewer = this;

      // Adding click event using mapper and filter..
      list.removeEvent('click').addEvent('click', function() {
         var filteredList = list.filter(filter, this);
         privViewer.open(filteredList.map(mapper), $extend({startImg: filteredList.indexOf(this)}, options));
         return false;
      });
   }

});


